package com.hexb.core.utils;

import com.hexb.core.enums.AscColors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;


/**
 * @Package : com.hexb.core.utils
 * @Author : hexb
 * @Date : 2018-08-16 15:52
 */
@Slf4j
public class RSAUtils {

    public static final String KEY_ALGORITHM = "RSA";
    public static final int KEY_SIZE = 1024;

    /**
     * RSA签名算法
     */
    private static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA";

    @Data
    public static class RSAPair {

        private RSAPublicKey publicKey;
        private RSAPrivateKey privateKey;

        private String publicKeyString;
        private String privateKeyString;

    }

    public static RSAPair genKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator rsa = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        rsa.initialize(KEY_SIZE);
        KeyPair keyPair = rsa.generateKeyPair();
        //甲方公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        RSAPair rsaPair = new RSAPair();
        rsaPair.setPrivateKey(privateKey);
        rsaPair.setPublicKey(publicKey);
        rsaPair.setPrivateKeyString(Base64Utils.encodeToString(privateKey.getEncoded()));
        rsaPair.setPublicKeyString(Base64Utils.encodeToString(publicKey.getEncoded()));
        return rsaPair;
    }


    public static String sign(String string, String privateKeyBase64) throws Exception {
        return Base64Utils.encodeToString(sign(string.getBytes(), privateKeyBase64));
    }

    public static boolean verify(String string, String publicKeyBase64, String sign) throws Exception {
        return verify(string.getBytes(), publicKeyBase64, sign);
    }

    /**
     * RSA私钥签名：签名方式SHA1withRSA
     *
     * @param data             待签名byte[]
     * @param privateKeyBase64 私钥（Base64编码）
     * @return 签名byte[]
     * @throws Exception
     */
    public static byte[] sign(byte[] data, String privateKeyBase64) throws Exception {
        // Base64 --> Key
        byte[] bytes = Base64Utils.decodeFromString(privateKeyBase64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        // Sign
        Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    /**
     * RSA公钥验签
     *
     * @param data            待签名字符串
     * @param publicKeyBase64 公钥（Base64编码）
     * @param sign 已签名字符串
     * @return 验签结果
     * @throws Exception
     */
    public static boolean verify(byte[] data, String publicKeyBase64, String sign) throws Exception {
        // Base64 --> Key
        byte[] bytes = Base64Utils.decodeFromString(publicKeyBase64);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        // verify
        Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(data);
        return signature.verify(Base64Utils.decodeFromString(sign));
    }


    public static String encryptByPublicKey(String plainText, String publicKeyString) throws Exception {
        return encryptByPublicKey(plainText, (RSAPublicKey) getPublicKey(publicKeyString));
    }

    /**
     * 公钥加密
     *
     * @param plainText 明文
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception
     */
    public static String encryptByPublicKey(String plainText, RSAPublicKey publicKey) throws Exception {
        if (publicKey == null) {
            throw new RuntimeException(String.format("%s=====>%s Public key is NULL", AscColors.RED, AscColors.END));
        }
        Cipher cipher;
        try {
            cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] output = cipher.doFinal(plainText.getBytes());
            return Base64Utils.encodeToString(output);
        } catch (Exception e) {
            log.error(String.format("%s=====>%s encrypt error,plain text is %s", AscColors.RED, AscColors.END, plainText));
            throw e;
        }
    }


    public static String decryptByPrivateKey(String cipherText, String privateKeyString) throws Exception {
        return decryptByPrivateKey(cipherText, (RSAPrivateKey) getPrivateKey(privateKeyString));
    }

    /**
     * 私钥解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception
     */
    public static String decryptByPrivateKey(String cipherText, RSAPrivateKey privateKey) throws Exception {
        if (privateKey == null) {
            throw new RuntimeException(String.format("%s=====>%s Private key is NULL", AscColors.RED, AscColors.END));
        }
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(Base64Utils.decodeFromString(cipherText));
            return new String(output);
        } catch (Exception e) {
            log.error(String.format("%s=====>%s encrypt error,plain text is %s", AscColors.RED, AscColors.END, cipherText));
            throw e;
        }
    }


    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64Utils.decodeFromString(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }


    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64Utils.decodeFromString(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

}
